#pragma once

#include <mysql/mysql.h>
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <jsoncpp/json/json.h>
#include "Log.hpp"

class json_util
{
public:
    static bool serialize(Json::Value& root, std::string& str)
    {
        Json::StyledWriter writer;
        str = writer.write(root);

        return true;
    }

    static bool deserialize(Json::Value& root, std::string& str)
    {
        Json::Reader reader;
        bool ret = reader.parse(str, root);
        if(ret == false)
        {
            LOG(ERROR, "deserialize fail");
            return false;
        }

        return true;
    }
};

class Mysql
{
public:
    Mysql(const std::string host, const std::string user, const std::string passwd, const std::string db, uint16_t port)
        : _host(host), _user(user), _passwd(passwd), _db(db), _port(port)
    {
    }
    void MysqlInit()
    {
        _my = mysql_init(nullptr);
        if (!_my)
        {
            LOG(FATAL, "mysql_init fail, errmsg: %s\n", strerror(errno));
            return;
        }

        if (!mysql_real_connect(_my, _host.c_str(), _user.c_str(), _passwd.c_str(), _db.c_str(), _port, nullptr, 0))
        {
            LOG(FATAL, "mysql_real_connect fail, errmsg: %s\n", strerror(errno));
            return;
        }

        int n = mysql_set_character_set(_my, "utf8");
        if (n != 0)
        {
            LOG(FATAL, "mysql_set_character_set fail, errmsg: %s\n", strerror(errno));
            return;
        }
    }

    bool MysqlQuery(std::string query)
    {
        if (mysql_query(_my, query.c_str()))
        {
            LOG(FATAL, "mysql_query: [%s] fail\n", query.c_str());
            return false;
        }
        return true;
    }

    void ShowRes()
    {
        MYSQL_RES *res = mysql_store_result(_my);
        int row = mysql_num_rows(res);
        int col = mysql_num_fields(res);

        MYSQL_FIELD *field = mysql_fetch_fields(res);
        for (int i = 0; i < col; ++i)
        {
            std::cout << field[i].name << "\t";
        }
        std::cout << std::endl;

        for (int i = 0; i < row; ++i)
        {
            MYSQL_ROW line = mysql_fetch_row(res);
            for (int j = 0; j < col; j++)
            {
                std::cout << line[j] << "\t";
            }
            std::cout << std::endl;
        }

        mysql_free_result(res);
    }

    void MysqlClose()
    {
        mysql_close(_my);
    }

    MYSQL *GetMy()
    {
        return _my;
    }

private:
    MYSQL *_my;
    const std::string _host;
    const std::string _user;
    const std::string _passwd;
    const std::string _db;
    uint16_t _port;
};

class StringSplit
{
public:
    static void split(std::string src, std::string sep, std::vector<std::string> &ret)
    {
        size_t pos = src.find(sep);
        size_t sep_len = sep.size();
        int begin = 0, end = 0;
        while (pos != std::string::npos)
        {
            end = pos - 1;
            std::string ans_str = src.substr(begin, end - begin + 1);
            if (ans_str.size() > 0)
                ret.push_back(ans_str);
            begin = end + sep_len + 1;
            pos = src.find(sep, begin);
        }
        if (begin != src.size())
        {
            ret.push_back(src.substr(begin));
        }
    }
};

class Fileop
{
public:
    static bool ReadFile(const std::string &path, std::string &message)
    {
        std::ifstream in(path, std::ios::binary);
        if(!in.is_open())
        {
            LOG(ERROR, "open file: %s fail\n", path.c_str());
            return false;
        }

        in.seekg(0, std::ios::end);
        int length = in.tellg();
        in.seekg(0, std::ios::beg);

        message.resize(length);
        in.read((char *)message.c_str(), length);

        in.close();
        return true;
    }

    static void WriteFile(const std::string &path, std::string message)
    {
        std::ofstream out(path, std::ios::binary | std::ios::app);

        out.write(message.c_str(), message.size());
        out.close();
    }
};

class UserCtl
{
private:
    static const int win_score = 30;
    static const int start_score = 1200;
    static const int sha2_num = 256;

    // std::string mysql_host = "101.34.254.139";
    // std::string mysql_user = "lw";
    // std::string mysql_passwd = "Liwei200408#";
    // std::string mysql_db = "gobang";
    // uint16_t Mysql_port = 3306;

public:
    UserCtl(const std::string host, const std::string user, const std::string passwd, const std::string db, 
    uint16_t port = 3306)
        : _my(host, user, passwd, db, port)
    {
        _my.MysqlInit();
    }
    ~UserCtl()
    {
        _my.MysqlClose();
    }

    // 新增用户
    // "张三" "123456"
    bool InsertUser(Json::Value &root)
    {
#define INSERT_USER "insert into user values(null, '%s', sha2('%s', %d), %d, 0, 0)"

        std::string username = root["username"].asString();
        std::string password = root["password"].asString();

        // if (SelectUserByName(root, username))
        // {
        //     LOG(ERROR, "this username not unique\n");
        //     return false;
        // }

        char buffer[1024];
        snprintf(buffer, sizeof(buffer), INSERT_USER, username.c_str(), password.c_str(), sha2_num, start_score);

        if (!_my.MysqlQuery(buffer))
        {
            LOG(ERROR, "insert user fail, sql: %s\n", buffer);
            return false;
        }
        return true;
    }

    // 登录验证，并返回详细信息
    // "张三" "123456"
    bool Login(Json::Value &root)
    {
#define SELECT_USER "select id, score, total_count, win_count from user where username='%s' and password=sha2('%s', %d)"

        std::string username = root["username"].asString();
        std::string password = root["password"].asString();
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), SELECT_USER, username.c_str(), password.c_str(), sha2_num);
        MYSQL_RES *res = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mtx);
            if (!_my.MysqlQuery(buffer))
            {
                LOG(ERROR, "Login fail, sql: %s\n", buffer);
                return false;
            }

            res = mysql_store_result(_my.GetMy());
            if (res == nullptr)
            {
                LOG(ERROR, "Login(): NOT find user %s\n", username.c_str());
                return false;
            }
        }
        int col_num = mysql_num_fields(res);
        if (col_num != 4)
        {
            LOG(ERROR, "Login return %d fields\n", col_num);
            return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        if(row == nullptr)
        {
            LOG(ERROR, "user: %s, mysql_fetch_row fields\n", username.c_str());
            return false;
        }

        // for(int i = 0; i < col_num; ++i)
        //     std::cout << row[i] << std::endl;

        root["id"] = std::stoi(row[0]);
        root["score"] = std::stoi(row[1]);
        root["total_count"] = std::stoi(row[2]);
        root["win_count"] = std::stoi(row[3]);

        mysql_free_result(res);
        return true;
    }

    bool SelectUserById(Json::Value &root, int id)
    {
#define SELECT_USERBYID "select username, password, score, total_count, win_count from user where id=%d"

        char buffer[1024];
        snprintf(buffer, sizeof(buffer), SELECT_USERBYID, id);
        MYSQL_RES *res = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mtx);
            if (!_my.MysqlQuery(buffer))
            {
                LOG(ERROR, "SelectUserById fail, sql: %s\n", buffer);
                return false;
            }

            res = mysql_store_result(_my.GetMy());
            if (res == nullptr)
            {
                LOG(ERROR, "SelectUserById(): NOT find user \n");
                return false;
            }
        }
        
        int col_num = mysql_num_fields(res);
        if (col_num != 5)
        {
            LOG(ERROR, "Login return %d fields\n", col_num);
            return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        if(row == nullptr)
        {
            LOG(ERROR, "id: %d, mysql_fetch_row fields\n", id);
            return false;
        }

        root["username"] = row[0];
        root["id"] = id;
        root["password"] = row[1];
        root["score"] = std::stoi(row[2]);
        root["total_count"] = std::stoi(row[3]);
        root["win_count"] = std::stoi(row[4]);

        mysql_free_result(res);
        return true;
    }

    bool SelectUserByName(Json::Value &root, std::string username)
    {
#define SELECT_USERBYNAME "select id, password, score, total_count, win_count from user where username='%s'"
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), SELECT_USERBYNAME, username.c_str());
        MYSQL_RES *res = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mtx);
            if (!_my.MysqlQuery(buffer))
            {
                LOG(ERROR, "SelectUserByName fail, sql: %s\n", buffer);
                return false;
            }

            res = mysql_store_result(_my.GetMy());
            if (res == nullptr)
            {
                LOG(ERROR, "SelectUserByName(): NOT find user %s\n", username.c_str());
                return false;
            }
        }
        
        int col_num = mysql_num_fields(res);
        if (col_num != 5)
        {
            LOG(ERROR, "Login return %d fields\n", col_num);
            return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        if(row == nullptr)
        {
            LOG(ERROR, "username: %s, mysql_fetch_row fields\n", username.c_str());
            return false;
        }

        root["username"] = username.c_str();
        root["id"] = std::stoi(row[0]);
        root["password"] = row[1];
        root["score"] = std::stoi(row[2]);
        root["total_count"] = std::stoi(row[3]);
        root["win_count"] = std::stoi(row[4]);

        LOG(INFO, "username: %s, uid: %d\n", username.c_str(), root["id"].asInt());

        mysql_free_result(res);
        return true;
    }

    bool Win(int id)
    {
        #define UPDATE_WIN "update user set score=score+30, total_count=total_count+1, win_count=win_count+1 where id=%d"

        char buffer[1024];
        snprintf(buffer, sizeof(buffer), UPDATE_WIN, id);
        if(!_my.MysqlQuery(buffer))
        {
            LOG(ERROR, "modify winner score fail\n");
            return false;
        }

        return true;
    }

    bool Lose(int id)
    {
        #define UPDATE_LOSE "update user set score=score-30, total_count=total_count+1 where id=%d"

        char buffer[1024];
        snprintf(buffer, sizeof(buffer), UPDATE_LOSE, id);
        if(!_my.MysqlQuery(buffer))
        {
            LOG(ERROR, "modify Loser score fail\n");
            return false;
        }

        return true;
    }

private:
    Mysql _my;
    std::mutex _mtx;
};
